package com.rxsk.cas.config;

import com.rxsk.cas.filter.JwtTokenFilter;
import com.rxsk.cas.properties.CasProperties;
import com.rxsk.cas.service.UserDetailsPlusService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

import javax.annotation.Resource;
import java.util.Arrays;

@EnableWebSecurity
@Configuration
@EnableConfigurationProperties(CasProperties.class)
public class CasSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private CasProperties casProperties;

    @Resource
    private UserDetailsPlusService userDetailsPlusService;

    @Resource
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider()).userDetailsService(userDetailsPlusService);
    }

    @Override
    public void configure(WebSecurity web) {
        //这里将所有的请求都设置为不被拦截, 是为了让前端请求接口的时候不被直接重定向到cas服务端登录页面
        //重定向到cas服务端登录页面的操作都由前端操作(前端直接根据token存在或过期决定是否重定向到cas服务端)
        web.ignoring().antMatchers("/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // .antMatchers("/**").anonymous() 这种写法不能将所有请求设置为不被拦截 会导致请求接口报 403错误
        // 需要采用 web.ignoring().antMatchers("/**")
        //具体原因是(我的理解): WebSecurity 主要针对的全局的url忽略规则, HttpSecurity 主要是权限控制规则

        http.cors()
                .and()
                .csrf()
                .disable()
                .authorizeRequests()
                /*.antMatchers("/**").anonymous()*/
                .antMatchers(casProperties.getIgnoredUrl()).anonymous()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                /*.accessDeniedHandler(casAccessDeniedHandler)*/
                .authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .addFilter(casAuthenticationFilter())
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
                .addFilterBefore(logoutFilter(), LogoutFilter.class);
    }

    /**
     * 配置CAS Client的属性
     */
    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();

        // 与CasAuthenticationFilter监视的URL一致
        serviceProperties.setService(casProperties.getClientLoginUrl());
        //serviceProperties.setServiceParameter(casProperties.getFilterUrlPattern());
        // 是否关闭单点登录，默认为false，所以也可以不设置。
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    /**
     * CAS认证入口，提供用户浏览器重定向的地址
     */
    @Bean
    @Primary
    public AuthenticationEntryPoint authenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        // CAS Server认证的登录地址
        entryPoint.setLoginUrl(casProperties.getServerLoginUrl());
        entryPoint.setServiceProperties(serviceProperties());
        return entryPoint;
    }

    /**
     * ticket校验，需要提供CAS Server校验ticket的地址
     */
    @Bean
    public TicketValidator ticketValidator() {
        // 默认情况下使用Cas20ProxyTicketValidator，验证入口是${casServerPrefix}/proxyValidate
        return new Cas20ProxyTicketValidator(casProperties.getServerUrlPrefix());
    }


    /**
     * cas认证处理逻辑
     */
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(userDetailsPlusService);
        provider.setKey("blurooo");
        return provider;
    }


    /**
     * 提供CAS认证专用过滤器，过滤器的认证逻辑由CasAuthenticationProvider提供
     */
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties());
        //filter.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler());
        filter.setAuthenticationManager(new ProviderManager(Arrays.asList(casAuthenticationProvider())));
        return filter;
    }

    /**
     * 接受cas服务端发出的注销请求
     */
    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(casProperties.getServerUrlPrefix());
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }

    /**
     * 将注销请求转发到cas server
     */
    @Bean
    public LogoutFilter logoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter(casProperties.getServerLogoutUrl(), new SecurityContextLogoutHandler());
        // 设置客户端注销请求的路径
        logoutFilter.setFilterProcessesUrl(casProperties.getServerLogoutUrl());
        return logoutFilter;
    }
}
